package cn.bearspark.idgenerator.provider.service.impl;

import cn.bearspark.idgenerator.enumeration.IdCodeEnum;
import cn.bearspark.idgenerator.provider.dao.mapper.IdGeneratorMapper;
import cn.bearspark.idgenerator.provider.dao.po.IdGeneratorPO;
import cn.bearspark.idgenerator.provider.service.IdGeneratorService;
import cn.bearspark.idgenerator.provider.service.bo.IdGeneratorBO;
import cn.bearspark.idgenerator.provider.service.bo.SeqIdGeneratorBO;
import cn.bearspark.idgenerator.provider.service.bo.UnSeqIdGeneratorBO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.*;

/**
 * @author f
 */
@Service
public class IdGeneratorServiceImpl implements IdGeneratorService, InitializingBean {

    private static final Logger LOGGER = LoggerFactory.getLogger(IdGeneratorServiceImpl.class);

    /**
     * 设置有序的 id 生成器的 {@code seq} 字段的值
     */
    private static final int SEQ = 1;

    /**
     * 抢占新的 id 段的比率
     * <p>
     * 当 已使用的 id 数 {@code used} >= 当前 id 段的总 id 数 {@code step} * {@code UPDATE_RATE} 时，
     * 触发抢占新的 id 段的操作
     */
    private static final float UPDATE_RATE = 0.75f;

    /**
     * 设置一个 id 生成器同时抢占 id 段的线程数量，默认一个 id 生成器每个时刻只能有 1 个线程来抢占 id 段
     */
    private static final int MAX_ACQUIRE_ID_SEGMENT_THREAD_SIZE = 1;

    /**
     * 设置最大重试抢占 id 段的次数，默认重试 3 次
     */
    private static final int MAX_RETRY_ACQUIRE_ID_SEGMENT_TIMES = 3;

    /**
     * 执行异步抢占 id 段操作的线程池，该线程池的大小是固定的，有 100 个线程
     */
    private static final ExecutorService ACQUIRE_ID_SEGMENT_THREAD_POOL = Executors.newFixedThreadPool(
            100,
            r -> new Thread(r, "bs-id-generator-thread-" + ThreadLocalRandom.current().nextInt(10000))
    );

    /**
     * id 生成器的业务对象的集合
     * <p>
     * key：id 生成器的业务码，value：该 id 生成器对应的业务对象
     */
    private final Map<Integer, IdGeneratorBO> idGeneratorBOMap = new HashMap<>();

    /**
     * 储存异步占领 id 段的任务的集合，做限流的操作
     * <p>
     * key：id 生成器的业务码，value：该 id 生成器对应的信号量
     */
    private final Map<Integer, Semaphore> semaphoreMap = new HashMap<>();

    private final IdGeneratorMapper idGeneratorMapper;

    public IdGeneratorServiceImpl(IdGeneratorMapper idGeneratorMapper) {
        this.idGeneratorMapper = idGeneratorMapper;
    }

    @Override
    public Long generateId(IdCodeEnum idCode) {
        int code = idCode.getCode();

        // 获取内存中的有序 id 生成器，如果获取失败，则返回 null
        IdGeneratorBO idGeneratorBO = idGeneratorBOMap.get(code);
        if (idGeneratorBO == null) {
            LOGGER.error("idGeneratorBO is null, code is {}", code);
            return null;
        }

        // 如果本 id 段耗尽了，则返回 null
        Long currId = idGeneratorBO.generateId();
        if (idGeneratorBO.isExhausted(currId)) {
            LOGGER.error("id is exhausted, code is {}", code);
            return null;
        }

        // 分发尝试占领新的 id 段的异步请求
        ensureSufficientId(idGeneratorBO);

        // 返回正确的 id
        return currId;
    }

    // 确保有 id 段有充足的 id
    private void ensureSufficientId(IdGeneratorBO idGeneratorBO) {
        int code = idGeneratorBO.getCode();
        long step = idGeneratorBO.getStep();
        long used = idGeneratorBO.fetchUsedNum();

        // 如果没有达到占领新id段的条件，则退出本方法
        if (!(used >= step * UPDATE_RATE)) {
            return;
        }

        // 使用 Semaphore，防止一个id生成器使用多个线程抢占id段
        Semaphore semaphore = semaphoreMap.get(code);
        if (semaphore == null) {
            LOGGER.error("semaphore is null, code is {}", code);
            return;
        }

        // 如果尝试获取许可失败，说明该 id 生成器已经有线程在占领 id 段，则无需继续占领 id 段
        if (!semaphore.tryAcquire()) {
            return;
        }

        // 异步执行占领 id 段的操作，加快速度，避免网络 IO 慢
        ACQUIRE_ID_SEGMENT_THREAD_POOL.execute(() -> {
            try {
                tryAcquireIdSegment(idGeneratorMapper.selectByCode(code));
            } catch (Exception e) {
                LOGGER.error("update interrupt, error is ", e);
            } finally {
                semaphoreMap.get(code).release();
            }
        });
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        List<IdGeneratorPO> idGeneratorPOList = idGeneratorMapper.list();
        for (IdGeneratorPO idGeneratorPO : idGeneratorPOList) {
            tryAcquireIdSegment(idGeneratorPO);
            semaphoreMap.put(idGeneratorPO.getCode(), new Semaphore(MAX_ACQUIRE_ID_SEGMENT_THREAD_SIZE));
        }
    }

    // 尝试让 id 生成器占领一个 id 段，并且把 id 生成器的业务对象放到对应的集合中
    private void tryAcquireIdSegment(IdGeneratorPO idGeneratePO) {
        Integer code = idGeneratePO.getCode();

        // 通知数据库本 id 段被占，如果通知成功，则退出
        // 这里不成功主要是因为 id 生成器有多个
        if (idGeneratorMapper.updateWithOptimisticLock(code, idGeneratePO.getVersion()) > 0) {
            putIdGenerator(idGeneratePO);
            return;
        }

        // 重试 3 次
        for (int i = 0; i < MAX_RETRY_ACQUIRE_ID_SEGMENT_TIMES; i++) {
            idGeneratePO = idGeneratorMapper.selectByCode(code);
            // 通知数据库本 id 段被占，如果通知成功，则退出
            if (idGeneratorMapper.updateWithOptimisticLock(code, idGeneratePO.getVersion()) > 0) {
                putIdGenerator(idGeneratePO);
                return;
            }
        }

        // 如果仍不成功，则报错
        throw new RuntimeException("表 id 段占用失败，竞争过于激烈, code is " + code);
    }

    // 将指定的 id 生成器放到集合中
    private void putIdGenerator(IdGeneratorPO idGeneratePO) {
        int code = idGeneratePO.getCode();
        long step = idGeneratePO.getStep();
        long begin = idGeneratePO.getStart();
        long end = begin + step;

        idGeneratorBOMap.put(code, SEQ == idGeneratePO.getSeq() ?
                new SeqIdGeneratorBO(code, step, begin, end) :
                new UnSeqIdGeneratorBO(code, step, begin, end));
    }
}
